/*
 * CoursePart.java
 *
 * Created on November 12, 2006, 1:38 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.entities;

import edu.umn.cs5115.scheduler.framework.Document;
import edu.umn.cs5115.scheduler.framework.KeyValueAdapter;
import edu.umn.cs5115.scheduler.framework.KeyValueCoding;
import edu.umn.cs5115.scheduler.framework.KeyValueObserver;
import edu.umn.cs5115.scheduler.framework.ManagedData;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * Represents a "Part" of a course.  It may be a section with children, a leaf
 * section, or the entire course itself.  In any event, it allows you to get
 * various bits and pieces of information from both Courses and Sections without
 * worrying about which it is.
 * @author grant
 */
public abstract class CoursePart extends ManagedData
{
    /** 
     * Key to listen to changes in this course part's children.  Subclasses that
     * implement getChildren() are responsible for ensuring that notifications
     * are sent out on this key at appropriate times. */
    public final static String CHILDREN_KEY = "children";
    /** Listen to for changes in the CoursePart's status. */
    public static final String STATUS_KEY         = "status";
    /** Listen to for changes in the CoursePart's brief status message. */
    public static final String STATUS_BRIEF_KEY   = "statusBrief";

    private Collection<Section> listenedSections = new HashSet();

    /** Listens to children's status to update our own when their's changes. */
    private KeyValueObserver statusListener = new KeyValueAdapter()
    {
        public void valueChanged(KeyValueCoding object, String key)
        {
            recalculateStatus();
        }
    };

    private Status status = Status.NONE;
    private String statusBrief = "";
    
    /**
     * Create a new course part.
     * @param document
     */
    protected CoursePart(Document document)
    {
        super(document);
        
        // Listen to our own sub section's key so we can add status 
        // listeners to them.
        addListener(new KeyValueAdapter()
        {
            public void valueChanged(KeyValueCoding object, String key, SetMutationType mutation, Set modifyingSet)
            {
                switch(mutation)
                {
                    case INTERSECT:
                        Set<Section> listenedToRemove = new HashSet(listenedSections);
                        listenedToRemove.retainAll(modifyingSet);
                        for(Section toRemove : listenedToRemove)
                        {
                            toRemove.removeListener(statusListener, Section.STATUS_KEY);
                            listenedSections.remove(toRemove);
                        }
                        break;
                    case MINUS:
                        listenedToRemove = modifyingSet;
                        for(Section toRemove : listenedToRemove)
                        {
                            toRemove.removeListener(statusListener, Section.STATUS_KEY);
                            listenedSections.remove(toRemove);
                        }
                        break;
                    case UNION:
                        Set<Section> addedSections = modifyingSet;
                        for(Section toAdd : addedSections)
                        {
                            toAdd.addListener(statusListener, Section.STATUS_KEY);
                            listenedSections.add(toAdd);
                        }
                        break;
                    case REPLACE:
                        for(Section toRemove : listenedSections)
                        {
                            toRemove.removeListener(statusListener, Section.STATUS_KEY);
                            listenedSections.remove(toRemove);
                        }
                        addedSections = modifyingSet;
                        for(Section toAdd : addedSections)
                        {
                            toAdd.addListener(statusListener, Section.STATUS_KEY);
                            listenedSections.add(toAdd);
                        }
                        break;
                }
            }
        }, CHILDREN_KEY);
    }
    
    /**
     * Get the CoursePart's parent. If this CoursePart is actually the entire
     * course, it does not have a parent.
     * @return The parent course part, or null if there is no parent.
     */
    public abstract CoursePart getParent();
    
    /**
     * Get the course this course part is a part of.
     * @return The course this course part is a part of.  If this course part
     * is actually the entire course, it returns itself.
     */
    public abstract Course getCourse();
    
    /**
     * Checks the descendants of this course to find if any of them are chosen.
     * @return True if some descendent is chosen, false if no descendants are
     * chosen.
     */
    public abstract boolean isAnyDescendantChosen();
    
    /**
     * Get the CoursePart's siblings.  If this CoursePart is an entire course,
     * it does not have any siblings.
     * @return The siblings for this course part.
     */
    public abstract Collection<CoursePart> getSiblings();
    
    /**
     * Get the direct children of this course part.  This does not include all
     * descendents; children's children are not included.
     * @return A collection of children.  If this a leaf node, an empty 
     * collection is returned.
     */
    public abstract Collection<CoursePart> getChildren();
    
    /**
     * Test to see if this course part is an ancestor of the given course part.
     * @param descendant The CoursePart you want to test for ancestory of.
     * @return True if this CoursePart is an ancestor of the specified 
     * CoursePart, in that there is some sequence of parents leading from the
     * descendant to this CoursePart, false otherwise.
     */
    public abstract boolean isAncestorOf(CoursePart descendant);
    
    /**
     * Test to see if this course part is a sibling of the given course.
     * @param sibling A possible sibling.
     * @return True if the two course parts share a common parent, false 
     * otherwise.  Note that a node is considered to be a sibling of itself.
     */
    public abstract boolean isSiblingOf(CoursePart sibling);
    
    /**
     * Test to see if this course paret is a direct child of another course part.
     * @param parent A possible parent.
     * @return True if the parent specified matches this course's parent, false
     * otherwise.
     */
    public abstract boolean isChildOf(CoursePart parent);
    
    /**
     * Recalculates the status and message.
     */
    protected abstract void recalculateStatus();
    
    /**
     * Get the course status.  The status determines whether or not the course
     * has children chosen, and if those children allow for registration in the
     * course or not.
     * @return The status of this course.
     * @see Status
     */
    public Status getStatus()
    {
        return status;
    }

    /**
     * Get a description of what the status means.  This is a brief, one-line
     * description explaining either that the user has selected everything they
     * need to for the course, or what sort of a problem will prevent them from
     * registering.
     * @return A brief description of the course's status.
     */
    public String getStatusBrief()
    {
        return statusBrief;
    }
    
    /** Update status and send out notifications if necessary.
     * @param newStatus The status that has changed.
     * @param newStatusBrief The new brief status message.
     */
    protected void setStatus(Status newStatus, String newStatusBrief)
    {
        boolean statusChanged = (status != newStatus);
        boolean statusBriefChanged = !statusBrief.equals(newStatusBrief);
        
        // assign here so that both are updated before any notifications are sent.
        status = newStatus;
        statusBrief = newStatusBrief;
        
        if (statusChanged);
            didChangeValueForKey(STATUS_KEY);
        
        if (statusBriefChanged)
            didChangeValueForKey(STATUS_BRIEF_KEY);
    }
}
